package com.ceair.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ceair.constant.SecurityConstants;
import com.ceair.entity.*;
import com.ceair.entity.model.response.Oauth2UserinfoResult;
import com.ceair.entity.model.security.CustomGrantedAuthority;
import com.ceair.entity.vo.RoleAuthorityVO;
import com.ceair.mapper.*;
import com.ceair.service.IOauth2BasicUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

import static com.ceair.constant.SecurityConstants.*;

/**
 * <p>
 * 基础用户信息表 服务实现类
 * </p>
 *
 * @author wangbaohai
 * @since 2024-11-19
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class Oauth2BasicUserServiceImpl extends ServiceImpl<Oauth2BasicUserMapper, Oauth2BasicUser> implements IOauth2BasicUserService, UserDetailsService {

    private final SysUserRoleMapper sysUserRoleMapper;

    private final SysAuthorityMapper sysAuthorityMapper;

    private final SysRoleAuthorityMapper sysRoleAuthorityMapper;

    private final Oauth2ThirdAccountMapper thirdAccountMapper;
    private Jwt token;

    @Override
    public UserDetails loadUserByUsername(String username) throws BadCredentialsException {
        // 在Security中“username”就代表了用户登录时输入的账号，在重写该方法时它可以代表以下内容：账号、手机号、邮箱、姓名等
        // “username”在数据库中不一定非要是一样的列，它可以是手机号、邮箱，也可以都是，最主要的目的就是根据输入的内容获取到对应的用户信息，如下方所示
        // 通过传入的账号信息查询对应的用户信息
        LambdaQueryWrapper<Oauth2BasicUser> wrapper =
                Wrappers.lambdaQuery(Oauth2BasicUser.class).or(o -> o.eq(Oauth2BasicUser::getEmail, username)).or(o -> o.eq(Oauth2BasicUser::getMobile, username)).or(o -> o.eq(Oauth2BasicUser::getAccount, username));
        Oauth2BasicUser basicUser = baseMapper.selectOne(wrapper);
        if (basicUser == null) {
            throw new BadCredentialsException("登录账号或者密码错误,请检查");
        }

        // 通过用户角色关联表查询对应的角色
        List<SysUserRole> userRoles =
                sysUserRoleMapper.selectList(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId,
                        basicUser.getId()));
        List<Long> rolesId =
                Optional.ofNullable(userRoles).orElse(Collections.emptyList()).stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
        if (ObjectUtils.isEmpty(rolesId)) {
            return basicUser;
        }
        // 通过角色菜单关联表查出对应的菜单
        List<SysRoleAuthority> roleMenus =
                sysRoleAuthorityMapper.selectList(Wrappers.lambdaQuery(SysRoleAuthority.class).in(SysRoleAuthority::getRoleId, rolesId));
        List<Long> menusId =
                Optional.ofNullable(roleMenus).orElse(Collections.emptyList()).stream().map(SysRoleAuthority::getAuthorityId).collect(Collectors.toList());
        if (ObjectUtils.isEmpty(menusId)) {
            return basicUser;
        }

        // 根据菜单ID查出菜单
        List<SysAuthority> menus = sysAuthorityMapper.selectByIds(menusId);
        Set<CustomGrantedAuthority> authorities =
                Optional.ofNullable(menus).orElse(Collections.emptyList()).stream().map(SysAuthority::getAuthority).map(CustomGrantedAuthority::new).collect(Collectors.toSet());
        basicUser.setAuthorities(authorities);
        return basicUser;
    }

    /**
     * 通过第三方账号保存用户信息
     * 当需要通过第三方账号信息来创建或更新用户信息时调用此方法
     *
     * @param thirdAccount 第三方账号信息，包含需要保存的用户相关数据
     * @return 返回保存后的用户ID，如果保存失败则返回null
     */
    @Override
    public Long saveByThirdAccount(Oauth2ThirdAccount thirdAccount) {
        // 创建一个新的基础用户对象，用于保存用户信息
        Oauth2BasicUser basicUser = new Oauth2BasicUser();

        // 将第三方账号的用户名复制到基础用户对象中
        basicUser.setName(thirdAccount.getName());
        // 将第三方账号的头像URL复制到基础用户对象中
        basicUser.setAvatarUrl(thirdAccount.getAvatarUrl());
        // 设置用户未删除状态
        basicUser.setDeleted(Boolean.FALSE);
        // 设置用户的来源类型，即第三方账号的类型
        basicUser.setSourceFrom(thirdAccount.getType());

        // 保存基础用户对象
        this.save(basicUser);

        // 返回保存后的用户ID
        return basicUser.getId();
    }

    @Override
    public Oauth2UserinfoResult getLoginUserInfo() {
        Oauth2UserinfoResult result = new Oauth2UserinfoResult();

        // 获取当前认证信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        log.info("获取当前认证信息：{}", authentication.getName());
        log.info("获取当前认证信息：{}", authentication.getPrincipal().toString());
        log.info("获取当前认证信息：{}", authentication.getAuthorities().toString());
        log.info("获取当前认证信息：{}", authentication.getDetails().toString());
//        log.info("获取当前认证信息：{}", authentication.getCredentials().toString());

        // 其它非token方式获取用户信息
        if (!(authentication instanceof JwtAuthenticationToken jwtAuthenticationToken)) {
            BeanUtils.copyProperties(authentication.getPrincipal(), result);
            result.setSub(authentication.getName());
            return result;
        }

        // 获取jwt解析内容
        Jwt token = jwtAuthenticationToken.getToken();

        // 获取当前登录类型
        String loginType = token.getClaim(OAUTH_LOGIN_TYPE);
        log.info("获取当前登录类型：{}", loginType);
        // 获取用户唯一Id
        String uniqueId = token.getClaimAsString(TOKEN_UNIQUE_ID);
        log.info("获取当前登录唯一Id：{}", uniqueId);
        // 基础用户信息id
        Long basicUserId = null;

        // 获取Token中的权限列表
        List<String> claimAsStringList = token.getClaimAsStringList(SecurityConstants.AUTHORITIES_KEY);
        log.info("获取当前登录权限列表：{}", claimAsStringList);

        // 如果登录类型不为空则代表是三方登录，获取三方用户信息
        if (!ObjectUtils.isEmpty(loginType)) {
            // 根据三方登录类型与三方用户的唯一Id查询用户信息
            LambdaQueryWrapper<Oauth2ThirdAccount> wrapper = Wrappers.lambdaQuery(Oauth2ThirdAccount.class)
                    .eq(Oauth2ThirdAccount::getUniqueId, uniqueId)
                    .eq(Oauth2ThirdAccount::getType, loginType);
            Oauth2ThirdAccount oauth2ThirdAccount = thirdAccountMapper.selectOne(wrapper);
            if (oauth2ThirdAccount != null) {
                basicUserId = oauth2ThirdAccount.getUserId();
                // 复制三方用户信息
                BeanUtils.copyProperties(oauth2ThirdAccount, result);
            }
        } else {
            // 为空则代表是使用当前框架提供的登录接口登录的，转为基础用户信息
            basicUserId = Objects.isNull(uniqueId) ? null : Long.valueOf(uniqueId);
        }

        if (basicUserId == null) {
            // 如果用户id为空，代表获取三方用户信息失败
            result.setSub(authentication.getName());
            return result;
        }

        // 查询基础用户信息
        Oauth2BasicUser basicUser = this.getById(basicUserId);
        if (basicUser != null) {
            BeanUtils.copyProperties(basicUser, result);
        }

        // 填充权限信息
        if (!ObjectUtils.isEmpty(claimAsStringList)) {
            Set<CustomGrantedAuthority> authorities = claimAsStringList.stream()
                    .map(CustomGrantedAuthority::new)
                    .collect(Collectors.toSet());
            // 否则设置为token中获取的
            result.setAuthorities(authorities);
        }

        result.setSub(authentication.getName());

        // 获取菜单权限信息
        result.setMenuAuthorities(this.getMenuAuthorities(basicUserId));

        // 获取按钮权限信息
        result.setButtons(this.getButtons(basicUserId));

        return result;
    }

    /**
     * 根据用户ID获取该用户拥有的按钮权限标识列表
     * 此方法通过调用获取系统权限的方法来收集按钮权限标识，旨在为特定用户筛选出有效的按钮权限
     *
     * @param id 用户ID，用于识别特定用户
     * @return 返回一个字符串列表，每个字符串代表该用户拥有权限的一个按钮标识
     */
    private List<String> getButtons(Long id) {
        return getSysAuthorities(id, 1).stream().filter(Objects::nonNull).map(SysAuthority::getBtnSign).toList();
    }

    /**
     * 获取菜单权限信息
     *
     * @param id 角色ID，用于获取特定角色的权限信息
     * @return 返回构建好的菜单权限树，以供角色权限展示或进一步处理
     */
    private List<RoleAuthorityVO> getMenuAuthorities(Long id) {
        // 构建并返回菜单权限树
        return buildRoleAuthorityTree(getSysAuthorities(id, 0));
    }

    /**
     * 根据用户ID和权限类型获取系统权限列表
     *
     * @param id 用户ID，用于查询该用户的角色和权限信息
     * @param type 权限类型，用于过滤返回的权限列表
     * @return 返回用户所拥有的系统权限列表，根据指定的权限类型进行过滤
     */
    private List<SysAuthority> getSysAuthorities(Long id, int type) {
        // 根据 用户id查询角色ids
        List<Long> roleIds =
                sysUserRoleMapper.selectList(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId, id))
                        .stream().filter(Objects::nonNull).map(SysUserRole::getRoleId).toList();

        if (roleIds.isEmpty()) {
            return new ArrayList<>();
        }

        // 根据 角色ids查询权限ids
        List<Long> authorityIds =
                sysRoleAuthorityMapper.selectList(Wrappers.lambdaQuery(SysRoleAuthority.class)
                                .in(SysRoleAuthority::getRoleId, roleIds))
                        .stream().filter(Objects::nonNull).map(SysRoleAuthority::getAuthorityId).toList();

        if (authorityIds.isEmpty()) {
            return new ArrayList<>();
        }

        // 根据 权限ids查询权限信息，并根据权限类型和菜单父ID进行过滤
        return sysAuthorityMapper.selectList(Wrappers.lambdaQuery(SysAuthority.class)
                        .in(SysAuthority::getId, authorityIds)
                        .eq(SysAuthority::getType, type))
                .stream().filter(Objects::nonNull).filter(item -> item.getMenuPid() != 0).toList();
    }

    /**
     * 将 List<SysAuthorityVO> 中所有菜单数据，复制到 RoleAuthorityVO 中，并根据 menuPid 建立父子关系
     *
     * @param sysList 所有系统菜单数据（平铺结构）
     * @return 树形结构的角色权限数据列表
     */
    private List<RoleAuthorityVO> buildRoleAuthorityTree(List<SysAuthority> sysList) {
        // 首先判空
        if (sysList == null || sysList.isEmpty()) {
            return Collections.emptyList();
        }

        // 第一步：复制 SysAuthorityVO 到 RoleAuthorityVO，复制相同属性
        List<RoleAuthorityVO> roleList = sysList.stream().filter(Objects::nonNull).map(sys -> {
            RoleAuthorityVO role = new RoleAuthorityVO();
            role.setId(sys.getId());
            // 截取 Authority 中的最后一个 【/】 符号后的 字符串作为 name
            role.setName(sys.getAuthority().substring(sys.getAuthority().lastIndexOf("/") + 1));
            role.setMenuPid(sys.getMenuPid());
            role.setTitle(sys.getName());
            role.setComponent(sys.getComponent());
            role.setPath(sys.getAuthority());
            role.setBtnSign(sys.getBtnSign());
            role.setHidden(sys.getHidden());
            role.setIcon(sys.getIcon());
            role.setSort(sys.getSort());
            role.setChildren(new ArrayList<>()); // 提前初始化 children 列表
            return role;
        }).toList();

        // 第二步：构造 Map，以 id 为键，便于快速查找父节点
        Map<Long, RoleAuthorityVO> map = roleList.stream()
                .collect(Collectors.toMap(RoleAuthorityVO::getId, role -> role));

        // 第三步：遍历 roleList，根据 menuPid 组装树形结构
        List<RoleAuthorityVO> tree = new ArrayList<>();
        for (RoleAuthorityVO role : roleList) {
            Long menuPid = role.getMenuPid();
            if (menuPid == null || menuPid == 1) {
                tree.add(role);
            } else {
                RoleAuthorityVO parent = map.get(menuPid);
                if (parent != null) {
                    parent.getChildren().add(role);
                } else {
                    // 记录日志或采取其他措施，确保不会丢失数据
                    log.info("Parent node not found for menuPid: {}", menuPid);
                }
            }
        }

        /**
         * 第四步：对 tree 进行排序，根据 sort 属性，并且需要递归对子节点进行排序
         */
        sortTree(tree);
        return tree;
    }

    /**
     * 对角色权限树进行排序
     *
     * @param tree 角色权限树，表示具有层级结构的角色权限信息列表
     *
     * 此方法的目的是对给定的角色权限树进行递归排序，首先对当前层级的节点进行排序，
     * 然后递归地对每个节点的子节点进行排序，以确保整个树结构按照预定义的顺序排列
     *
     * 使用递归的原因是角色权限树具有层级结构，每一层的节点可能还包含下一层级的节点，
     * 通过递归可以确保对树中的所有节点进行排序处理
     */
    public void sortTree(List<RoleAuthorityVO> tree) {
        if (tree == null || tree.isEmpty()) {
            return; // 防止空指针异常
        }

        // 对当前层级进行排序
        tree.sort(Comparator.comparingInt(RoleAuthorityVO::getSort));

        // 递归对子节点进行排序
        for (RoleAuthorityVO node : tree) {
            List<RoleAuthorityVO> children = node.getChildren(); // 子节点存储在 children 字段中
            if (children != null && !children.isEmpty()) {
                sortTree(children); // 递归调用
            }
        }
    }

}
